package com.zhenzhang.myblog.service;

import com.fasterxml.jackson.core.io.JsonEOFException;
import com.zhenzhang.myblog.dao.ArticleDao;
import com.zhenzhang.myblog.entity.Article;
import com.zhenzhang.myblog.entity.MultiPageInfo;
import com.zhenzhang.myblog.entity.role.User;
import com.zhenzhang.myblog.exception.JsonException;
import com.zhenzhang.myblog.utils.AssetsManager;
import com.zhenzhang.myblog.utils.FileUtils;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

@Service
public class ArticleService {


    @Resource
    private ArticleDao articleDao;

    /**
     * 根据用户id和文章关键词获取文章列表分页
     * @param aid   用户ID，可为null，当为null时表示获取全站所有的文章列表
     * @param keyWord 关键词，可为null
     * @param page  页数，从1开始计算
     * @param size  每页的大小
     * @throws IllegalArgumentException 当页数小于1或页大小小于1时抛出
     * @return  文章列表分页信息
     */
    public MultiPageInfo<Article> getArticleList(Integer aid, String keyWord, int page, int size) {
        validPageNum(page, size);
        int offset = size * (page - 1);
        MultiPageInfo<Article> info = new MultiPageInfo<>();
        info.setPage(page);
        info.setSize(size);
        if (aid == null && (keyWord == null || keyWord.equals(""))) {
            // 获取数据库中所有文章
            info.setTotal(articleDao.getAllArticleListCount());
            List<Article> article_list = articleDao.getAllArticleList(size, offset);
            setType2List(article_list);
            info.setList(article_list);
        } else if(aid == null) {
            // 根据关键词获取文章
            info.setTotal(articleDao.getAllArticleListCountByKeyWord(keyWord));
            List<Article> result_list = articleDao.getArticleListByKeyWord(keyWord, size, offset);
            setType2List(result_list);
            info.setList(result_list);
        } else if(keyWord == null || keyWord.equals("")) {
            // 根据作者id获取文章
            info.setTotal(articleDao.getArticleListCountByAid(aid));
            List<Article> article_list = articleDao.getArticleListByAid(aid, size, offset);
            setType2List(article_list);
            info.setList(article_list);
        }else {
            // 根据关键词和作者id获取文章
            info.setTotal(articleDao.getArticleListCountByAidAndKeyWord(aid,keyWord));
            List<Article> result_list = articleDao.getArticleListByKeyWordAndAid(aid, keyWord, size, offset);
            setType2List(result_list);
            info.setList(result_list);
        }
        return info;
    }

    /**
     * 根据文章类型和用户id获取文章列表
     * @param aid   用户id
     * @param type  文章类型
     * @param page  页数
     * @param size  每页大小
     * @return  文章列表分页信息
     */
    public MultiPageInfo<Article> getArticleListByType(Integer aid, String type, int page, int size) {
        validPageNum(page, size);
        int offset = size * (page - 1);
        MultiPageInfo<Article> info = new MultiPageInfo<>();
        info.setPage(page);
        info.setSize(size);
        if(aid == null) {
            info.setTotal(articleDao.getArticleListCountByType(type));
            List<Article> article_list = articleDao.getArticleByType(type, size, offset);
            setType2List(article_list);
            info.setList(article_list);
        }else {
            info.setTotal(articleDao.getArticleListCountByTypeAndAid(type, aid));
            List<Article> article_list = articleDao.getArticleByTypeAndAid(type, aid, size, offset);
            setType2List(article_list);
            info.setList(article_list);
        }
        return info;
    }

    /**
     * 获取草稿文章列表
     * @param aid   作者id
     * @param page  页数
     * @param size  每页大小
     * @return  文章列表
     */
    public MultiPageInfo<Article> getDraftsArticleList(Integer aid, int page, int size) {
        if (aid == null) {
            throw new JsonException(400,"aid不能为空");
        }
        validPageNum(page,size);
        int offset = size * (page - 1);
        MultiPageInfo<Article> info = new MultiPageInfo<>();
        info.setPage(page);
        info.setSize(size);
        info.setTotal(articleDao.getEditingArticleListCountByAid(aid));
        List<Article> article_list = articleDao.getEditingArticleListByAid(aid,size,offset);
        setType2List(article_list);
        info.setList(article_list);
        return info;
    }

    /**
     * 判断页数和每页大小是否合法
     * @param page  页数
     * @param size  每页大小
     */
    public void validPageNum(int page, int size) {
        // 判断传入的page和size是否合法
        if (page < 1) {
            throw new IllegalArgumentException("无效的页数" + page + "，小于1");
        } else if (size < 1) {
            throw new IllegalArgumentException("无效的页大小" + size + "，小于1");
        }
    }

    public void addArticle(Article article, HttpSession session) {
        if(article.getTitle() == null || article.getContent() == null) {
            throw new JsonException(400,"文章标题或文章内容不能为空");
        }
        var html = article.getAbstract0();
        String abstract0 = removeHtml(html);
        //如果正文长度大于50则摘要截取正文前50个字符,否则摘要等于正文
        if (abstract0.length() > 50) {
            article.setAbstract0(abstract0.substring(0,50)+"...");
        } else {
            article.setAbstract0(abstract0);
        }
        // 如果传入的文章已存在 则说明用户进行了保存操作之后再发布
        // 如果文章不存在但文章对象title字段和该用户的其它文章标题相同则报错422
        // 如果文章不存在则说明用户没有保存操作直接发布
        int exists = articleDao.isArticleTitleExists(article.getTitle(), article.getAid(), article.getId());
        int idExists = articleDao.isArticleExists(article.getId());
        if (idExists == 1) {
            article.setEditing(0);
            updateArticle(article,session);
            return;
        }else if (exists == 0) {
            articleDao.addArticle(article);
        }else {
            throw new JsonException(422,"不能使用重复标题，请更换标题");
        }
        Integer article_id = articleDao.getArticleIdByTitleAndAid(article.getTitle(),article.getAid());
        if(article.getType() != null) {
            List<String> typeList = article.getType();
            addArticleType(typeList, article_id);
        }
    }

    /**
     * 添加文章类型
     * @param typeList 类型列表
     * @param article_id 文章id
     */
    public void addArticleType(List<String> typeList, Integer article_id){
        if(typeList.size()>5) {
            throw new JsonException(400,"标签不能超过5个");
        }
        for (String s : typeList) {
            if (s.length() > 20) {
                throw new JsonException(400, "标签长度不能超过20");
            }
            if (articleDao.TypeExist(s) == 0) {
                articleDao.addType(s);
            }
            Integer type_id = articleDao.getTypeId(s);
            articleDao.addArticleType(article_id, type_id);
        }
    }

    /**
     * 修改文章类型
     * @param typeList  类型列表
     * @param article_id 文章id
     */
    public void updateArticleType(List<String> typeList, Integer article_id) {
        // 修改前数据库中的type列表,并把里面的英文字符转换为小写
        typeList = changeList2LowerCase(typeList);
        List<String> articleTypeList = articleDao.getTypeById(article_id);
        // 若传入的type无变化则跳过修改操作
        if(articleTypeList.equals(typeList)) {
            return;
        }
        for (int i = 0; i < typeList.size(); i++) {
            if(typeList.get(i).length() > 20) {
                throw new JsonException(400,"标签长度不能超过20");
            }
            else {
                // 若传入的typeList列表中的某一项不存在于数据库中，则数据库添加该type并且添加映射
                if(articleDao.TypeExist(typeList.get(i)) == 0) {
                    articleDao.addType(typeList.get(i));
                    Integer type_id = articleDao.getTypeId(typeList.get(i));
                    articleDao.addArticleType(article_id, type_id);
                    // 添加完成后更新数据库type列表以便下面的删除操作
                    articleTypeList = articleDao.getTypeById(article_id);
                }
                // 若传入typeList列表中的某一项存在于数据库但无对该文章的映射则添加映射
                Integer type_id = articleDao.getTypeId(typeList.get(i));
                if(articleDao.TypeExist(typeList.get(i)) == 1
                        && articleDao.typeMapperExist(article_id,type_id) == 0) {
                    articleDao.addArticleType(article_id, type_id);
                    articleTypeList = articleDao.getTypeById(article_id);
                }
                // 若数据库中传入的文章id对应的文章的type不存在于传入的typeList列表中，则数据库删除该type的映射
                for (int j = 0; j < articleTypeList.size(); j++) {
                    System.out.println(articleTypeList.get(j).toLowerCase());
                    if(typeList.contains(articleTypeList.get(j).toLowerCase())){
                        continue;
                    }
                    type_id = articleDao.getTypeId(articleTypeList.get(j));
                    articleDao.deleteArticleType(article_id, type_id);
                    // 删除映射后如果该type没有与其映射的文章则将其删除
                    if(articleDao.articleTypeExist(type_id) == 0) {
                        articleDao.deleteType(articleTypeList.get(j));
                    }
                    articleTypeList = articleDao.getTypeById(article_id);
                    j = 0;
                }
            }
        }
    }

    public void updateArticle(Article article, HttpSession session) {
        User user = ((User) session.getAttribute("loginUser"));
        Integer id = user.getId();
        if(!id.equals(article.getAid()) && !user.getAdmin()) {
            throw new JsonException(403,"用户权限不足!");
        }
        if(article.getTitle() == null || article.getContent() == null) {
            throw new JsonException(400,"文章标题或文章内容不能为空");
        }
        var html = article.getAbstract0();
        String abstract0 = removeHtml(html);
        if (abstract0.length() > 50) {
            article.setAbstract0(abstract0.substring(0,50)+"...");
        } else {
            article.setAbstract0(abstract0);
        }
        if (articleDao.isArticleTitleExists(article.getTitle(), article.getAid(), article.getId()) == 0) {
            articleDao.updateArticle(article);
        } else {
            throw new JsonException(422,"已存在改标题,请更换标题");
        }
        Integer article_id = articleDao.getArticleIdByTitleAndAid(article.getTitle(),article.getAid());
        if(article.getType() != null) {
            List<String> typeList = article.getType();
            updateArticleType(typeList, article_id);
        }
        // 如果更新时传入的标签列表为空 则删除库中所有该文章的标签映射
        if(article.getType().isEmpty() || article.getType() == null) {
            List<String> articleTypeList = articleDao.getTypeById(article_id);
            articleDao.deleteAllArticleTypeById(article_id);
            // 删除映射后还要检查数据库中是否存在没有映射的类型，若有则删除该类型
            for (String s : articleTypeList) {
                Integer type_id = articleDao.getTypeId(s);
                if (articleDao.articleTypeExist(type_id) == 0) {
                    articleDao.deleteType(s);
                }
            }
        }
    }

    public void saveArticle(Article article, HttpSession session) {
        System.out.println(article.getId());
        if (articleDao.isArticleExists(article.getId()) == 0) {
            addArticle(article,session);
        } else {
            updateArticle(article,session);
        }
    }

    /**
     * 删除文章
     * @param article  文章对象
     */
    public void deleteArticle(Article article, HttpSession session) {
        User user = ((User) session.getAttribute("loginUser"));
        Integer userId = user.getId();
        // 若不是管理员且文章作者id和登录用户id不一致则报错
        if(!userId.equals(article.getAid()) && !user.getAdmin()) {
            throw new JsonException(403,"用户权限不足!");
        }
        deleteArticleCover(article.getCover());
        deleteArticleImage(article.getId());
        articleDao.deleteArticle(article.getId());
    }

    /**
     * 批量删除文章，包括图片和封面
     * @param list  文章id数组
     */
    public void batchDeleteArticle(ArrayList<Integer> list) {
        String coverName;
        if (list.size() == 0) {
            throw new JsonException(400, "您未选择文章");
        }
        for (Integer id : list) {
            coverName = articleDao.getArticleCover(id);
            deleteArticleCover(coverName);
            deleteArticleImage(id);
        }
        articleDao.batchDeleteArticle(list);
    }

    /**
     * 设置封面,将图片文件重命名为{文章id}.文件后缀,然后保存至项目运行目录
     * @param id 文章id
     * @param file  前端表单传入的文件
     * @throws JsonException 文件大小超过3MB和上传不支持其格式的文件时抛出（代码400）
     */
    public void setCover(Integer id, MultipartFile file) {
        if (id == null) {
            throw new JsonException(400,"文章id不能为空");
        }
        String suffix = validImage(file);
        try{
            String coverFileName = id + "." + suffix;
            Path savePath = AssetsManager.getArticleCoverPath(coverFileName);
            file.transferTo(savePath);
        }catch (IOException e) {
            throw new JsonException(500,e.getMessage());
        }
    }

    /**
     * 将文章图片保存至项目运行目录
     * @param id    文章id
     * @param file 前端表单传入的文件
     * @throws JsonException 出现IO错误抛出500
     */
    public void setArticleImage(Integer id, MultipartFile file) throws IOException {
        Path dir_path = AssetsManager.getArticleImagePath(id);
        if(!Files.exists(dir_path)) {
            Files.createDirectories(dir_path);
            System.out.println("已创建文件夹:" + dir_path);
        }
        validImage(file);
        try {
            if(file.getOriginalFilename() == null) {
                throw new JsonException(400,"文件名不能为空！");
            }
            Path savePath = AssetsManager.getArticleImagePath(file.getOriginalFilename(), id);
            // 若已存在同名文件则删除该文件
            deleteExistsFile(String.valueOf(savePath));
            file.transferTo(savePath);
        } catch (IOException e) {
            throw new JsonException(500, e.getMessage());
        }
    }

    /**
     * 获取文章图片Resource资源列表
     * @param id    文章id
     * @param fileName  文件名
     * @return  图片文件在本地文件系统中的UrlResource对象。如果文章没有图片或文章不存在则返回null
     */
    public UrlResource getArticleImageResource(Integer id, String fileName) {
        if (id == null || fileName == null) {
            return null;
        }
        Path path = AssetsManager.getArticleImagePath(fileName,id);
        if(!Files.exists(path) || Files.isDirectory(path)) {
            return null;
        }
        try {
            return new UrlResource(path.toUri());
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除文章图片文件夹以及其中所有文件
     * @param id    文章id
     */
    public void deleteArticleImage(Integer id) {
        if (id == null) {
            throw new JsonException(400,"待删除文章id不能为空");
        }
        Path path = AssetsManager.getArticleImagePath(id);
        File f = new File(String.valueOf(path));
        if(!f.isDirectory()) {
            System.out.println("不是一个文件夹，无操作");
            return;
        }
        File[] listFiles = f.listFiles();
        // 文件夹为空直接删除文件夹
        if(listFiles == null) {
            if(f.delete()){
                System.out.println("成功删除路径为:" + path + " 的图片文件夹");
            }
            return;
        }
        // 删除文件夹中所有文件
        for (File file:listFiles) {
            file.delete();
        }
        // 删除文件夹
        if (f.delete()) {
            System.out.println("成功删除路径为:" + path + " 的图片文件夹");
        }
    }

    public void deleteArticleImage(String fileName, Integer id) {
        if (id == null || fileName == null) {
            throw new JsonException(400,"待删除文件名或文章id不能为空");
        }
        Path path = AssetsManager.getArticleImagePath(fileName,id);
        File f = new File(String.valueOf(path));
        if (f.delete()) {
            System.out.println("成功删除路径为:" + path + " 的图片文件");
        }
    }

    public void deleteArticleCover(String fileName) {
        Path path = AssetsManager.getArticleCoverPath(fileName);
        File f = new File(String.valueOf(path));
        if (f.delete()) {
            System.out.println("成功删除路径为:" + path + " 的封面文件");
        }
    }


    /**
     * 获取文章封面Resource资源
     * @param id 文章id
     * @return 封面文件在本地文件系统中的UrlResource对象。如果文章没有封面或文章不存在则返回null
     */
    public UrlResource getArticleCoverResource(Integer id, String fileName) {
        if (id == null) {
            return null;
        }
        Path path = AssetsManager.getArticleCoverPath(fileName);
        if (!Files.exists(path) || Files.isDirectory(path)) {
            return null;
        }
        try {
            return new UrlResource(path.toUri());
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public UrlResource getArticleCoverResource(Integer id) {
        String cover = articleDao.getArticleCover(id);
        if (id == null) {
            return null;
        }
        Path path = AssetsManager.getArticleCoverPath(cover);
        if (!Files.exists(path) || Files.isDirectory(path)) {
            return null;
        }
        try {
            return new UrlResource(path.toUri());
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据文章列表中每篇文章的id获取其类型列表并将类型列表set进文章对象
     * @param articleList 文章列表
     */
    public void setType2List(List<Article> articleList) {
        for (Article article : articleList) {
            List<String> typeList = articleDao.getTypeById(article.getId());
            article.setType(typeList);
        }
    }

    public List<String> changeList2LowerCase(List<String> list) {
        List<String> temp = new ArrayList<>();
        for (String s : list) {
            temp.add(s.toLowerCase());
        }
        list = temp;
        return list;
    }

    /**
     * 判断上传的文章封面和文章图片是否合法，若合法则返回图片文件后缀名
     * @param file  图片文件
     * @throws JsonException 文件大小超过3MB和上传不支持其格式的文件时抛出（代码400）
     * @return 文件后缀名
     */
    public String validImage(MultipartFile file) {
        if(file.getSize() > 1024*1024*3) {
            throw new JsonException(400,"文件不得超过3MB");
        }
        String originFilename = file.getOriginalFilename();
        if (originFilename == null) {
            originFilename = "a.jpg";
        }
        String suffix = FileUtils.getSuffix(originFilename);
        if (!FileUtils.contain(suffix)) {
            throw new JsonException(400,"仅支持jpg,jpeg,gif,png格式文件");
        }
        return suffix;
    }

    /**
     * 传入即将上传文件的文件路径，若该路径存在文件则删除该同名文件
     * @param path  文件路径
     */
    public void deleteExistsFile(String path) {
        File e = new File(path);
        if (e.exists()) {
            if(e.isFile()) {
                if(e.delete()) {
                    System.out.println("成功删除路径为:" + path + " 的文件");
                }else {
                    throw new JsonException(500,"文件删除失败!");
                }
            }
        }
    }

    /**
     * 传入文件夹路径，删除文件夹内不存在于传入的文件列表中的文件
     * @param path  文件夹路径
     * @param files 文件列表
     */
    public void deleteExistsFile(String path,MultipartFile[] files) {
        File e = new File(path);
        if (e.exists()) {
            if(e.isDirectory()) {
                File[] file_list = e.listFiles();
                if (file_list == null) {
                    return;
                }
                for (File i : file_list) {
                    // 如果指定文件夹中的文件列表file_list中的文件不存在于前端传入的files，则删除
                    if(isContain(files,i) == 0) {
                        if(i.delete()) {
                            System.out.println("成功删除路径为:" + i.getAbsolutePath() + " 的文件");
                        } else {
                            throw new JsonException(500,"删除文件失败");
                        }
                    }
                }
            }
        }
    }

    /**
     * 判断文件是否存在于传入的文件列表
     * @param file_list 文件列表
     * @param file  待判断文件
     * @return  判断结果，返回1为存在，返回0为不存在
     */
    public int isContain(MultipartFile[] file_list, File file) {
        for (MultipartFile f : file_list) {
            String file_name = f.getOriginalFilename();
            if (file_name == null) {
                throw new JsonException(400,"文件名不能为空!");
            }
            if (file_name.equals(file.getName())) {
                return 1;
            }
        }
        return 0;
    }

    /**
     * 获取html中的纯文本
     * @param html  html代码
     * @return  纯文本
     */
    private String removeHtml(String html) {
        String content = "";
        content = html.replaceAll( ".*?<body.*?>(.*?)<\\/body>", "$1");
        content=content.replaceAll("</?[a-zA-Z]+[^><]*>","");
        content.replaceAll("\n","");
        return content;
    }
}
